package app.services.profit;

import app.Const;
import app.constant.MemberConstant;
import app.kit.TypeKit;
import app.models.member.MemberProduct;
import app.models.member.ProfitDetails;
import app.models.order.TradeMoney;
import app.models.product.ProductYields;
import com.google.common.base.Optional;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.IAtom;
import goja.Logger;
import goja.StringPool;
import goja.kits.ArithKit;
import org.joda.time.DateTime;
import org.joda.time.Days;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.sql.Timestamp;

import static app.Const.FIELD_AMOUNT;
import static app.Const.FIELD_ORDER_ID;
import static app.Const.FIELD_PRODUCT;
import static app.Const.HUNDRED;

/**
 * <p>
 *
 * </p>
 *
 * @author sogYF
 * @version 1.0
 * @since JDK 1.6
 */
public final class ProfitService {
    /**
     * 获取单例对象,如果要调用该单例的使用,只能通过该方法获取.
     */
    public static final ProfitService me = new ProfitService();

    /**
     * 私有构造函数,确保对象只能通过单例方法来调用.
     */
    private ProfitService() {
    }


    public void onceTimeCollection(final MemberProduct memberProduct) {
        int memberId = TypeKit.getInt(memberProduct, Const.FIELD_MEMBER);
//        int time_limit = TypeKit.getInt(memberProduct, "time_limit");
        // 起息日期
        DateTime productValueDate = TypeKit.getDateTime(memberProduct, "valuedate");
        if (productValueDate == null) {
            Logger.error("产品起息日期不存在，无法计算收益");
            return;
        }
        DateTime productDueTime = TypeKit.getDateTime(memberProduct, "due_date");
        if (productDueTime == null) {
            Logger.error("产品到期日期不存在，无法计算收益");
            return;
        }
        //  购买本金
        BigDecimal amount = memberProduct.getBigDecimal(FIELD_AMOUNT);
        // 产品信息
        int product = TypeKit.getInt(memberProduct, FIELD_PRODUCT);

        // 一次性还本付息
        // 已经到期，并符合规定的计算时间，那么就开始计算收益
        // 查询最后的收益率
        ProductYields yields = ProductYields.dao.findByLastUpdate(product);
        BigDecimal yield = yields.getBigDecimal(Const.FIELD_YIELD);
        // 计算持有天数
        int days = Days.daysBetween(productValueDate, productDueTime).getDays();
        if (days < 0) {
            Logger.error("产品起息日起和结息日期不正确，无法确定收益计算");
            return;
        }
        // 包含起息日
        days = days + 1;

        int basic_days = memberProduct.getNumber("basic_days").intValue();
        final BigDecimal profit = ProfitService.me.onceProfit(amount, yield, basic_days, days);
        int memberProductId = memberProduct.getNumber(StringPool.PK_COLUMN).intValue();
        // 3. 收益进入打款表
        final TradeMoney tradeMoney = TradeMoney.dueProfitMoney(memberId, memberProduct.getStr("membercode"), product,
                memberProductId, amount, profit);
        tradeMoney.set(FIELD_ORDER_ID, TypeKit.getInt(memberProduct, FIELD_ORDER_ID));
        // 更新会员产品状态为打款中
        memberProduct.set(MemberProduct.FIELD_PLAYMONEY_STATUS, MemberConstant.PLAYMONEY_SUBMIT);
        final ProfitDetails profitDetails = ProfitDetails.dao.onetimePrifit(memberId, product, memberProductId, profit,
                DateTime.now(), amount);
        boolean ok = Db.tx(new IAtom() {
            @Override
            public boolean run() throws SQLException {
                return tradeMoney.save() && profitDetails.save() && memberProduct.update();
            }
        });
        if (ok) {
            Logger.info("写入打款纪录成功，后续会进行打款确认操作！");
        } else {
            Logger.error("收益计算打款纪录操作失败 {'memberProduct':{},'product':{}, 'member': {} }", memberProductId, product, memberId);
        }
    }

    /**
     * 一次性到期支付计算
     *
     * @param memberProduct 会员产品
     * @param year          年
     * @param month         月
     * @param day           日
    //     * @param hour          小时
     */
    public void onceTimeCollection(final MemberProduct memberProduct,
                                   int year, int month, int day/*, int hour*/) {

        int memberId = TypeKit.getInt(memberProduct, Const.FIELD_MEMBER);

        Timestamp productValuedate = memberProduct.getTimestamp("valuedate");

        int valuedate_year, valuedate_month, valuedate_day, due_time_year, due_time_month, due_time_day;
//        DateTime statDate;
        if (productValuedate == null) {
            Logger.error("起息日期不存在，无法计算收益");
            return;
//            // 表示没有起息日期，则按成交日计算
//            // 产品成交日期
//            DateTime deal_time = new DateTime(memberProduct.getTimestamp("deal_time").getTime());
//            // 计息方式 T+0 还是 1
//            int valueoffset = memberProduct.getNumber("valuedate_offset").intValue();
//            statDate = deal_time;
//            if (valueoffset > 0) {
//                statDate = statDate.plusDays(valueoffset);
//            }
//
//
//            int time_limit = memberProduct.getNumber(Const.FIELD_TIME_LIMIT).intValue();
//            String time_limit_unit = memberProduct.getStr(Const.FIELD_TIME_LIMIT_UNIT);
//            DateTime dueDate;
//            if (StringUtils.equals(time_limit_unit, DictConstant.MONTH_UNIT)) {
//                //  理财期限单位为月
//                dueDate = statDate.plusMonths(time_limit);
//            } else {
//                dueDate = statDate.plusDays(time_limit);
//            }
//
//            //  计算起息日期和到期日期
//            valuedate_year = statDate.getYear();
//            valuedate_month = statDate.getMonthOfYear();
//            valuedate_day = statDate.getDayOfMonth();
//            due_time_year = dueDate.getYear();
//            due_time_month = dueDate.getMonthOfYear();
//            due_time_day = dueDate.getDayOfMonth();
        } else {
            // 有起息日期，则按照起息日期来进行计算
            valuedate_year = TypeKit.getInt(memberProduct, "valuedate_year");
            valuedate_day = TypeKit.getInt(memberProduct, "valuedate_day");
            valuedate_month = TypeKit.getInt(memberProduct, "valuedate_month");
            due_time_year = TypeKit.getInt(memberProduct, "due_time_year");
            due_time_month = TypeKit.getInt(memberProduct, "due_time_month");
            due_time_day = TypeKit.getInt(memberProduct, "due_time_day");
        }


        //  购买本金
        BigDecimal amount = memberProduct.getBigDecimal(FIELD_AMOUNT);
        // 产品信息
        int product = memberProduct.getNumber(FIELD_PRODUCT).intValue();

//        int clearing_day = memberProduct.getNumber("clearing_day").intValue();
//        int clearing_time = memberProduct.getNumber("clearing_time").intValue();

        // 一次性还本付息
        if (year == due_time_year && month == due_time_month && day == due_time_day /*+ clearing_day && hour == clearing_time*/) {
            // 已经到期，并符合规定的计算时间，那么就开始计算收益
            // 查询最后的收益率
            ProductYields yields = ProductYields.dao.findByLastUpdate(product);
            BigDecimal yield = yields.getBigDecimal(Const.FIELD_YIELD);
            // 计算持有天数
            int days = Days.daysBetween(
                    new DateTime(valuedate_year, valuedate_month, valuedate_day, 0, 0),
                    new DateTime(due_time_year, due_time_month, due_time_day, 0, 0)).getDays();

            int basic_days = memberProduct.getNumber("basic_days").intValue();
            final BigDecimal profit = ProfitService.me.onceProfit(amount, yield, basic_days, days);
            int memberProductId = memberProduct.getNumber(StringPool.PK_COLUMN).intValue();
            // 3. 收益进入打款表
            final TradeMoney tradeMoney = TradeMoney.dueProfitMoney(memberId, memberProduct.getStr("membercode"), product,
                    memberProductId, amount, profit);
            tradeMoney.set(FIELD_ORDER_ID, TypeKit.getInt(memberProduct, FIELD_ORDER_ID));
            // 更新会员产品状态为打款中
            memberProduct.set(MemberProduct.FIELD_PLAYMONEY_STATUS, MemberConstant.PLAYMONEY_SUBMIT);
            final ProfitDetails profitDetails = ProfitDetails.dao.onetimePrifit(memberId, product, memberProductId, profit,
                    new DateTime(year, month, day, 0, 0), amount);
            boolean ok = Db.tx(new IAtom() {
                @Override
                public boolean run() throws SQLException {
                    return tradeMoney.save() && profitDetails.save() && memberProduct.update() ;
                }
            });
            if (ok) {
                Logger.info("写入打款纪录成功，后续会进行打款确认操作！");
            } else {
                Logger.error("收益计算打款纪录操作失败 {'memberProduct':{},'product':{}, 'member': {} }", memberProductId, product, memberId);
            }
        }
    }

    /**
     * 通过年化利率来反推万份收益
     *
     * @param yield 年化利率
     * @return 万份收益
     */
    public double getMillionIncome(float yield) {
        return ArithKit.div(yield, 3.65, 4);
    }

    /**
     * 通过万份收益得到年化利率，
     *
     * @param millionIncome 万份收益
     * @return 年化利率
     */
    public double getYield(float millionIncome) {
        return ArithKit.mul(millionIncome, 3.65);
    }

    /**
     * 收益模式为本息滚动，的计算
     *
     * @param amount        投资金额
     * @param millionIncome 万份收益
     * @return 收益
     */
    public double rollProfit(double amount, float millionIncome) {
        return ArithKit.round(ArithKit.mul(ArithKit.div(amount, 10000), millionIncome), 2);
    }

    /**
     * 到期一次性支付的利息计算
     *
     * @param amount    投资金额
     * @param yield     年化利率
     * @param basidays  基本天数,365或者360.
     * @param hold_days 持有天数
     * @return 收益
     */
    public BigDecimal onceProfit(BigDecimal amount, BigDecimal yield, int basidays, int hold_days) {

       return amount.multiply(yield.divide(HUNDRED, 4, BigDecimal.ROUND_HALF_UP))
               .multiply(new BigDecimal(hold_days))
               .divide(new BigDecimal(basidays), 2, BigDecimal.ROUND_HALF_UP);
    }


    /**
     * 等额本息(利随本清法)<br>
     * Average Capital plus Interest Method
     * <p/>
     * <pre>
     * 每月还款额=贷款本金/贷款期月数+（本金-已归还本金累计额）×月利率
     * </pre>
     *
     * @param capital      贷款本金
     * @param month        月数
     * @param rate         月利率
     * @param installments 期次
     * @return 收益详情
     */
    public final Optional<ProfitDto> averageCapitalInterest(double capital, int month, double rate, int installments) {
        double a = Math.pow(1 + rate, month);
        double pay = capital * rate * a / (a - 1);

        BigDecimal payPerMonth = new BigDecimal(Math.round(pay * 100))
                .divide(HUNDRED,2, BigDecimal.ROUND_HALF_UP);
//        List<ProfitDto> pays = Lists.newArrayListWithCapacity(month);
        // 月利率
        BigDecimal _rate = new BigDecimal(rate);
        // 资金
        BigDecimal _capital = new BigDecimal(capital);
        for (int i = 1; i <= month; i++) {
            //利息
            BigDecimal interest = reserve2(_capital.multiply(_rate));
            // 应付本金不得超过剩余本金
            BigDecimal payCapital = payPerMonth.subtract(interest);
            if (payCapital.compareTo(_capital) > 0) {
                payCapital = _capital;
            }
            _capital = _capital.subtract(payCapital);
            if (i == installments) {
                return Optional.of(ProfitDto.createDto(i, new BigDecimal(0), payCapital, interest, _capital));
            }
//            pays.add(ProfitDto.createDto(i, 0, payCapital.doubleValue(), interest.doubleValue(), _capital.doubleValue()));

        }
        return Optional.absent();
    }


    /**
     * 等额本金<br>
     * Average Capital Method
     * <p/>
     * <pre>
     * 即贷款期每月以相等的额度平均偿还贷款本息，每月还款计算公式为：
     * 每月还款额=贷款本金×月利率×（1+月利率）还款月数/[（1+月利率）还款月数-1]
     * </pre>
     *
     * @param capital 贷款本金
     * @param month   月数
     * @param rate    月利率
     * @param installments 期次
     * @return 收益详情
     */
    public final Optional<ProfitDto> averageCapital(double capital, int month, double rate, int installments) {
        // 资金
        BigDecimal _capital = new BigDecimal(capital);
        // 月利率
        BigDecimal _rate = new BigDecimal(rate);
        BigDecimal monthCaptical = reserve2(new BigDecimal(capital * 1.0 / month));

        BigDecimal payed = new BigDecimal(0);
        for (int i = 1; i <= month; i++) {
            BigDecimal interst = reserve2(_capital.subtract(payed).multiply(_rate));
            ProfitDto profitDto;
            if (i == month) {
                profitDto = ProfitDto.createDto(i, _capital.subtract(payed), interst);
            } else {
                profitDto = ProfitDto.createDto(i, monthCaptical, interst);
            }
            if (i == installments) {
                return Optional.of(profitDto);
            }
            payed = payed.add(monthCaptical);
        }
        return Optional.absent();
    }


    public static BigDecimal reserve2(BigDecimal num) {
        return new BigDecimal(num.multiply(HUNDRED).intValue()).divide(HUNDRED,2,BigDecimal.ROUND_HALF_UP);
    }

    /**
     * lazy 加载的内部类,用于实例化单例对象.
     */
    private static class ProfitServiceHolder {
        static ProfitService instance = new ProfitService();
    }
}
